2oneweek.dev

22. 참조하는 this를 바꿀 수 있는 방법

    Tags

  • JavaScript
22. 참조하는 this를 바꿀 수 있는 방법 thumbnail

This 관련 메소드 (call, apply, bind)

this와 call()

call 메소드 사용

  • call 메소드는 getTotal.call()와 같이 함수.call 형태로 사용한다.
  • getTotal.call(this, 10, 20)과 같이 파라미터를 넘길 수 있다.
    • 첫번째 인자는 파라미터로 넘어가지 않고, 두번째 인자 10부터 파라미터로 넘어가게 된다.
    • 첫번째 인자는 호출된 함수에서 this로 참조할 오브젝트를 명시한다. (모든 오브젝트가 가능하다.)
  • 호출하면서 첫번째 인자를, 실행 컨텍스트의 this바인딩 컴포넌트로 바인딩하겠다는 목적으로 사용한다.

예시

var value = 100; function get(param) { console.log(this); return param + this.value; } var result = get.call(this, 20); console.log(result);
  • get함수의 console.log(this)는 window를 반환하게 된다.

    • call 메소드의 인자로 this를 넘겼는데, 이때의 this는 Global Object인 window다.

    • 따라서 window의 value 값인 100과 param으로 넘긴 20이 더해진 120이 반환된다.

      Object를 this로 참조하게 만들면 아래와 같다.

      var obj = { value: 80 }; var result = get.call(obj, 20); console.log(result); //=> 100
      • 위와 같이 call 메소드에 this로 참조할 오브젝트를 obj로 바꾸면, get함수는 obj를 this로 참조하게 된다. 따라서 100이 반환된다.

this로 참조할 오브젝트를 변경할 수 있는 것이 call의 특징이다.


primitive 타입을 바인딩하는 call

function get() { return this.valueOf(); } var result = get.call(123); console.log(result);
  • this가 object를 참조해야하므로, 숫자를 작성하면 에러가 발생할 것 같지만, 그렇지 않다.
  1. 값(123)의 타입에 해당하는 Number 인스턴스를 생성하고, 123을 primitive 값으로 설정한 후에 넘기게 된다.
  2. 엔진은 this바인딩 컴포넌트에 Number 객체의 인스턴스를 바인딩한다.
  3. 함수에서는 this.valueOf로 primitive 값을 꺼낼 수 있다.

this 참조 변경

var book = { value: 123, point: { value: 456, get() { console.log(this.value); }, }, };

잠깐 정리하자, this는 작성된 위치에 따라 결정되는 것이 아니라, 호출한 오브젝트가 누구냐에 따라 결정된다. 저 코드만 봤을 때, this.value는 자동으로 456일 것 같지만, 우리가 get을 호출할 때, book.point.get()을 사용하고, this가 point 객체에 바인딩되기 때문에 this.value가 456임을 알아야 한다. 이 this는 call과 같은 함수에 의해 바뀔 수 있기 때문에, 저런 코드만 보고 this가 누굴 참조하는지 결정할 수 없다.


var book = { value: 123, point: { value: 456, get() { console.log(this.value); }, }, }; book.point.get.call(book); //=> 123 book.point.get.call(book.point); //=> 456
  • book.point.get.call(book) //=> 123 call 메소드의 첫번째 매개변수가 book이다. 따라서 이때, get 함수의 this는 book 오브젝트가 되고 value : 123을 가져오게 된다.

  • book.point.get.call(book.point); //=> 456 call 메소드의 첫번째 매개변수가 book.point이다. 따라서, 이때 get 함수의 this는 book안에 위치한 point 오브젝트가 된다.(book은 point 오브젝트의 경로를 알려주는 의미뿐이다.) value : 456을 가져오게 된다.




this와 apply()

apply 사용

  • getTotal.apply(this,[10,20])과 같이 사용법은 call메소드와 다르지 않다. 다만 파라미터가 배열로 들어가야 된다.
  • apply는 파라미터 수가 유동적일 때 사용하면 된다. call메소드는 파리미터 수가 고정일 때 사용하면 된다.
    • 웹페이지에서 유저가 선택한 항목에 대한 처리를 하는 메소드가 있을 때, 어떤 유저는 세개를 선택할 수도 있고 어떤 유저는 다섯개를 선택할 수도 있다. 이런 처리를 하는 함수의 this바인딩을 변경하고 싶으면 apply를 쓰면 된다.

apply와 arguments 프로퍼티 사용

var obj = { 0: 10, 1: 20, 2: 30 }; var data = [4, 5, 6]; function get() { for (i = 0; i < arguments.length; i++) { console.log(arguments[i], this[i]); } } get.apply(obj, data);
  • get 메소드는 파라미터 수가 유동적이므로 따로 파라미터를 작성하지 않고, argument 프로퍼티를 이용해서 받는다.(arguments 객체는 모든 함수 내에서 이용가능한 지역변수다. arguments는 array-like이지 array가 아니다. 따라서 Array의 prototype에 있는 메소드를 사용할 수 없다.) function test() { for (i = 0; i < arguments.length; i++) { console.log(arguments[i]); } } test(1, 2, 3); //=> 1 2 3 test(1); //=> 1
  1. get.apply(obj, data) 호출이 실행되면, 엔진은 실행 컨텍스트의 this 바인딩 컴포넌트에 obj를 바인딩한다.
  2. 엔진은 함수 안에 arguments 오브젝트를 만든다. 그리고 argument 오브젝트에, 파라미터를 key-value 형태로 저장한다

파라미터 수가 가변적이고 this를 바인딩 하고 싶을 때, call과 argument를 써도 가능 할 것같다.

apply와 call을 쓰는 이유는, 데이터 중심적으로 처리하기 위함이다.

  • 데이터를 바꿔가면서 함수를 처리하겠다. 근데 베이스가 되는 데이터는 this로 제공하고, 가변적인 값은 인자로 넘겨줄 것인데, 그걸 call과 apply로 하면 편하다.
  • 여기에 인스턴스와 프로토타입의 개념까지 추가하면 더 넓은 범위, 더 다양한 데이터 처리가 용이해진다.


this와 콜백 함수

  • ES5의 map(), forEach() 메소드 처럼 콜백함수가 있는 메소드는 두 번째 파라미터에 this로 참조할 오브젝트를 작성할 수 있다.
    • ES5에는 콜백함수를 가지는 메소드가 7개 존재한다.
var obj = { value: 100 }; function get(data) { return data.map(function (elem, idx, data) { return elem + this.value; }, obj); //this 바인딩 } var result = get([5, 6, 7]); console.log(result); //[105,106,107]

내 생각에, this는 연산을 하는 메소드에서 베이스가 되는 값을 동적으로 바꿔주는데 용이한 것 같다. 1을 더하는 메소드였지만, 코드 실행 과정 중에, 100을 더하는 메소드로 바꾸고 싶다면, this 바인딩을 통해 바꿔줄 수도 있고, 인스턴스 마다 베이스가 되는 값을 따로 들고 있을 수도 있어서 편리한 것 같다.

  • 위의 코드에서도 알 수 있다. Callback 함수는 독립적인 기능을 수행하고 있으며, 그것의 베이스가 되는 값 100을 this 바인딩을 통해 설정해준다.

  • 또한, 함수가 독립성을 갖게된다. this, 파라미터 등을 넘겨주면서, 어느 오브젝트에 속해 있는지에 대한 제약사항이 감소함




this와 bind()

  • bind 메소드는 묶는 것이다.
    • this로 참조할 오브젝트와 파라미터 값을 묶는다. this와 파라미터, 그리고 함수를 묶어서 새로운 function Object를 반환해준다.
    • call, apply 뿐 아니라, 자동적인 this 바인딩과 차이는 무엇인가
  • bind 메소드는 두 번에 나눠 처리한다.
    1. function Object 생성과 초기화
    2. 생성한 function Object를 함수로 호출 일반적으로 함수를 호출하면 바로 실행하지만(물론 실행 컨텍스트 만드는 건 당연), bind는 위와 같이 두 단계로 나눠서 처리한다. 각각의 단계에서 바인딩이 발생한다. 우선 묶기 위해서 단계를 나눴다고 생각하자

bind 메소드 사용

  • bind의 첫 번째 파라미터는, 함수에서 this로 참조할 오브젝트다.
  • bind의 두 번째 파라미터는, 호출된 함수에 전달할 값이다.

function 오브젝트 생성과 호출

var book = { point: 123, get() { return this.point; }, }; var obj = book.get.bind(book); console.log(typeof obj); //새로운 function 반환 var result = obj(); console.log(result);
  • var obj = book.get.bind(book)

    • book.get 함수를 호출하는데, bind메소드를 사용했다.
    1. book.get함수를 호출하지 않고, 엔진은 새로운 function Object를 생성한다.
    2. bind를 호출한 (여기서는 get)function Object의 내부 프로퍼티 [[BountTargetFunction]]에 "새로 생성한 function Object"를 설정한다.

    1. 새로 생성한 function Object의 내부 프로퍼티 [[BoundThis]]에 bind 메소드의 첫번째 파라미터(book)를 설정한다.

      • get() 함수에서 this로 참조할 오브젝트를 설정하는 것이다.
      • get 앞에 작성된 오브젝트를 this로 참조하지 않는다. bind 의 첫 번째 파라미터가 없는 경우 this는 undefined이 된다.
      • 지금까지의 단계는 모두, 생성된 function Object가 나중에 호출되니까, 현재 상황을 미리 저장하는 과정이라 생각하면 된다.
    2. 새롭게 생성한 function Object를 변수 obj에 할당한다.

      • 즉, this를 커스텀 바인딩한 function Object를 새로 만들어 두는 것이고, 나중에 호출하는 것이다.
  • var result = obj()

    1. bind()가 생성한 function Object를 호출하게 된다.
    2. this가 bind 메소드로 바인딩된 book.get()함수가 호출된다.
  • return this.point

    1. this가 [[BountThis]]에 담긴 것을 참조한다.
    2. [[BoundThis]]book오브젝트를 참조하고 있었다.
    3. 따라서 book 오브젝트의 point 값인 123을 사용하여 반환한다.
var title = "글로벌"; function getTitle(param) { console.log(param, this.title); } var newObj = { title: "새 오브젝트" }; var bindedFunc = getTitle.bind(newObj, 99); bindedFunc(); //=> 99 "새 오브젝트" getTitle(); //=> undefined "글로벌"

bind 사용과 파라미터 병합

var book = { get() { return Array.prototype.slice.call(arguments); }, }; var obj = book.get.bind(this, 10, 20); var result = obj(30, 40); console.log(result);
  • Array.prototype.slice.call(arguments);

    • get함수는 인자를 새로운 배열로 만들어서 반환하려고 한다.
    • 인자에 대해서 slice를 하면 되지만, 엔진이 만든 arguments는 Array-like 이지, Array의 인스턴스가 아니기 때문에, Javascript Array객체가 제공하는 메소드를 사용할 수 없다.
    • 따라서 Array의 prototype에 연결된 slice 메소드를 호출하는데, slice를 사용할 환경(상태)을 call로 바인딩 해준다.
    • 즉, Array-like인 arguments 객체에 대해서 slice를 사용할 수 있게 해주는 코드다.

  • var obj = book.get.bind(this, 10, 20);

    • 엔진은 function Object를 하나 생성하고 get function Object의 [[TargetFunction]]에 새로 생성한 function Object를 할당한다.
    • 새로 생성한 function Object의 [[BoundThis]]에는 bind의 첫번재 파라미터를 넘겨준다.(여기서는 window 오브젝트네)
    • 새로 생성한 function Object의 [[BountArguments]]에 파라미터 값을 넘긴다.
    • bind가 새로 생성한 function Object를 obj에 할당한다. bind가 강제로 this를 바인딩한 get메소드가 된다.
  • var result = obj(30, 40);

    • 엔진은 obj를 호출하고 실행컨텍스트로 넘어가기 전에, 파라미터를 bind한다. 파라미터 병합



bind의 활용, 이벤트 처리

시나리오

  • 시나리오 "값 출력" 버튼을 클릭하면 값을 표시한다.
  • HTML의 형태 <script src="point.js" defer></script> <button id="point">값 출력</button>

이벤트처리에서 bind의 활용

  • 이벤트 처리의 어려움은, 이벤트를 설정할 때(onClick 또는 addEventListener)의 오브젝트를 핸들러에서 this로 참조할 수 없다는 것입니다. 이 때 bind()를 이용해서 해결할 수 있다.
  • handler함수에서 this를 사용하고자 할 때 bind를 쓴다고 알고 있자. 핸들러 함수는 DOM 요소에 달려있지만, 그 바깥의 객체를 기억하고자 할 때 this가 필요할 수 있다 이때 bind 쓴다.
  • 핸들러 함수에서 bind를 쓰면, DOM 요소에 있는 값 말고, 전역변수 말고, 다른 객체의 값에 접근하기 위한 길을 하나 더 갖게 된다.
  • 여러가지 핸들러가 한 객체에 모여있을 때, 핸들러 함수끼리 프로퍼티를 공유하게 할 수도 있겠다.
var book = { myPoint: 100, setEvent() { // var node는 element 오브젝트 var node = document.getElementById("point"); node.onclick = this.show.bind(book, node); //핸들러 함수 설정 }, show(node, event) { //핸들러 함수 console.log(node.textContent); console.log(this.myPoint); }, }; book.setEvent();
  • 이렇게 객체 하나에 묶어서 핸들러를 관리하는 것도 좋은 방법이다.
body = document.querySelector("body"); test = document.createElement("button"); test.textContent = "테스트"; body.appendChild(test); function func() { console.log(this); } var obj = {}; var newFunc = func.bind(obj); test.addEventListener("click", func); //=> Click시 element를 출력 test.addEventListener("click", newFunc); //=> Click시, obj를 출력
Written by@2-one-week
현재 블로그 개발 중

GitHubLinkedIn